Recall that a single process is capable of hosting multiple application domains via the static AppDomain.CreateDomain() method. While creating new app domains on the fly is a rather infrequent task for most .NET applications, it is important to understand the basics of doing so. For example, as you will see later in this text, when you build dynamic assemblies (see Chapter 17) you will need to install them into a custom app domain. As well, several .NET security APIs require you to understand how to construct new app domains to isolate assemblies based on supplied security credentials.
To investigate how to create new application domains on the fly (and how to load new assemblies into these custom homes), create a new Console Application named CustomAppDomains. The AppDomain.CreateDomain() method has been overloaded a number of times. At minimum, you will specify the friendly name of the new application domain to be constructed. Update your Program class with the following code: Here, you are leveraging the ListAllAssembliesInAppDomain() method from the previous example, however this time you are passing in the AppDomain object to analyze as an incoming argument:
class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with Custom App Domains *****\n"); // Show all loaded assemblies in default app domain. AppDomain defaultAD = AppDomain.CurrentDomain; ListAllAssembliesInAppDomain(defaultAD); // Make a new app domain. MakeNewAppDomain(); Console.ReadLine(); } private static void MakeNewAppDomain() { // Make a new AppDomain in the current process and // list loaded assemblies. AppDomain newAD = AppDomain.CreateDomain("SecondAppDomain"); ListAllAssembliesInAppDomain(newAD); } static void ListAllAssembliesInAppDomain(AppDomain ad) { // Now get all loaded assemblies in the default app domain. var loadedAssemblies = from a in ad.GetAssemblies() orderby a.GetName().Name select a; Console.WriteLine("***** Here are the assemblies loaded in {0} *****\n", ad.FriendlyName); foreach (var a in loadedAssemblies) { Console.WriteLine("-> Name: {0}", a.GetName().Name); Console.WriteLine("-> Version: {0}\n", a.GetName().Version); } } }
If you run the current example, you will see that the default application domain (CustomAppDomains.exe) has loaded mscorlib.dll, System.dll, System.Core.dll and CustomAppDomains.exe, given the C# code base of the current project. However, the new application domain only contains mscorlib.dll, which as you recall is the one .NET assembly which is always loaded by the CLR for each and every application domain:
***** Fun with Custom App Domains ***** ***** Here are the assemblies loaded in CustomAppDomains.exe ***** -> Name: CustomAppDomains -> Version: 1.0.0.0 -> Name: mscorlib -> Version: 4.0.0.0 -> Name: System -> Version: 4.0.0.0 -> Name: System.Core -> Version: 4.0.0.0 ***** Here are the assemblies loaded in SecondAppDomain ***** -> Name: mscorlib -> Version: 4.0.0.0
Note If you debug this project (via F5), you will find many additional assemblies are loaded into each AppDomain which are used by the Visual Studio debugging process. Running this project (via Ctrl + F5) will display only the assemblies directly by each app domain.
This may seem counterintuitive if you have a background in traditional Windows (as you might suspect, both application domains have access to the same assembly set). Recall, however, that an assembly loads into an application domain, not directly into the process itself.
The CLR will always load assemblies into the default application domain when required. However, if you do ever manually create new app domains, you can load assemblies into said app domain using the AppDomain.Load() method. Also, be aware that the AppDomain.ExecuteAssembly() method can be called to load an *.exe assembly and execute the Main() method.
Assume that you wish to load CarLibrary.dll into your new secondary app domain. Provided you have copied this library to the \bin\Debug folder of the current application you could update the MakeNewAppDomain() method as so (be sure to import the System.IO namespace, to gain access to the FileNotFoundException class):
private static void MakeNewAppDomain() { // Make a new AppDomain in the current process. AppDomain newAD = AppDomain.CreateDomain("SecondAppDomain"); try { // Now load CarLibrary.dll into this new domain. newAD.Load("CarLibrary"); } catch (FileNotFoundException ex) { Console.WriteLine(ex.Message); } // List all assemblies. ListAllAssembliesInAppDomain(newAD); }
This time, the output of the program would appear as so (note the presence of CarLibrary.dll):
***** Fun with Custom App Domains ***** ***** Here are the assemblies loaded in CustomAppDomains.exe ***** -> Name: CustomAppDomains -> Version: 1.0.0.0 -> Name: mscorlib -> Version: 4.0.0.0 -> Name: System -> Version: 4.0.0.0 -> Name: System.Core -> Version: 4.0.0.0 ***** Here are the assemblies loaded in SecondAppDomain ***** -> Name: CarLibrary -> Version: 2.0.0.0 -> Name: mscorlib -> Version: 4.0.0.0
Note Remember! If you debug this application, you will see many additional libraries loaded into each application domain.
It is important to point out that the CLR does not permit unloading individual .NET assemblies. However, using the AppDomain.Unload() method, you are able to selectively unload a given application domain from its hosting process. When you do so, the application domain will unload each assembly in turn.
Recall that the AppDomain type defines the DomainUnload event, which is fired when a custom application domain is unloaded from the containing process. Another event of interest is the ProcessExit event, which is fired when the default application domain is unloaded from the process (which obviously entails the termination of the process itself).
If you wish to programmatically unload newAD from the hosting process, and be notified when the associated application domain is torn down, you could update MakeNewAppDomain() with the following additional logic:
private static void MakeNewAppDomain() { // Make a new AppDomain in the current process. AppDomain newAD = AppDomain.CreateDomain("SecondAppDomain"); newAD.DomainUnload += (o, s) => { Console.WriteLine("The second app domain has been unloaded!"); }; try { // Now load CarLibrary.dll into this new domain. newAD.Load("CarLibrary"); } catch (FileNotFoundException ex) { Console.WriteLine(ex.Message); } // List all assemblies. ListAllAssembliesInAppDomain(newAD); // Now tear down this app domain. AppDomain.Unload(newAD); }
If you wish to be notified when the default application domain is unloaded, modify your Main() method to handle the ProcessEvent event of the default application domain:
static void Main(string[] args) { Console.WriteLine("***** Fun with Custom App Domains *****\n"); // Show all loaded assemblies in default app domain. AppDomain defaultAD = AppDomain.CurrentDomain; defaultAD.ProcessExit += (o, s) => { Console.WriteLine("Default AD unloaded!"); }; ListAllAssembliesInAppDomain(defaultAD); MakeNewAppDomain(); Console.ReadLine(); }
That wraps up our look at the .NET application domain. To conclude this chapter, let’s look at one further level of partitioning, which is used to group objects into contextual boundaries.
Source Code The CustomAppDomains project is included under the Chapter 16 subdirectory.